package org.budo.support.spring.web.argument.resolver;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

import javax.servlet.http.HttpServletRequest;

import org.budo.support.fastjson.Fastjson;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.mvcs.Mvcs;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.alibaba.fastjson.JSONObject;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class BudoArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Method method = parameter.getMethod();

        Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        for (Annotation[] annotations : parameterAnnotations) {
            for (Annotation annotation : annotations) {
                if (RequestBody.class.equals(annotation)) {
                    log.debug("#32 not supportsParameter, parameter={}", parameter);
                    return false; // 有 RequestBody 就不用这个
                }
            }
        }

        log.debug("#38 supportsParameter, parameter={}", parameter);
        return true;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest nativeRequest = (HttpServletRequest) webRequest.getNativeRequest();

        String requestBody = Mvcs.getRequestBody(nativeRequest);
        if (null == requestBody || requestBody.isEmpty()) {
            return this.resolveArgumentNoRequestBody(parameter, webRequest);
        }

        JSONObject requestJson = Fastjson.parseObject(requestBody);
        if (null == requestJson) {
            return this.resolveArgumentNoRequestBody(parameter, webRequest);
        }

        Object parameterValue = this.resolveArgument(requestJson, parameter);
        Object parameterToType = this.parameterToType(parameterValue, parameter, webRequest);
        return parameterToType;
    }

    public Object resolveArgumentNoRequestBody(MethodParameter parameter, NativeWebRequest webRequest) {
        HttpServletRequest nativeRequest = (HttpServletRequest) webRequest.getNativeRequest();
        String parameterName = parameter.getParameterName();

        String parameterValue = Mvcs.getRequestParameter(nativeRequest, parameterName);
        if (null != parameterValue) {
            Object parameterToType = this.parameterToType(parameterValue, parameter, webRequest);
            return parameterToType;
        }

        log.error("#57 resolveArgument, requestBody=" + Mvcs.getRequestBody(nativeRequest) + ", uri=" + Mvcs.getRequestURI(nativeRequest) + ", parameter=" + Mvcs.getRequestParameterMap(nativeRequest) //
                + ", request=" + nativeRequest + ", this=" + this);
        return null;
    }

    public Object resolveArgument(JSONObject requestJson, MethodParameter parameter) {
        String parameterName = parameter.getParameterName();
        return requestJson.get(parameterName);
    }

    public Object parameterToType(Object valueIn, MethodParameter parameter, NativeWebRequest webRequest) {
        HttpServletRequest nativeRequest = (HttpServletRequest) webRequest.getNativeRequest();
        String requestBody = Mvcs.getRequestBody(nativeRequest);

        String parameterName = parameter.getParameterName();
        Class<?> parameterType = parameter.getParameterType();
        Type genericType = parameter.getGenericParameterType();

        try {
            Object valueOut = ReflectUtil.toType(valueIn, parameterType, genericType);
            return valueOut;
        } catch (Exception e) {
            throw new IllegalArgumentException("#71 _value=" + valueIn + ", parameterType=" + parameterType + ", genericType=" + genericType + ", parameterName=" + parameterName //
                    + ", handler=" + Mvcs.getHandler() + ", uri=" + Mvcs.getRequestURI(nativeRequest) + ", requestBody=" + requestBody, e);
        }
    }
}